<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<style>
    body {
        /* background: #000; */
    }

    * {
        user-select: none;
        box-sizing: border-box;
    }

    .preview {
        width: 500px;
        height: 500px;
        min-width: 500px;
        border: 1px dashed #000;
        position: relative;
        font-size: 0;
        overflow: hidden;
    }

    .preview img {
        width: 100%;
        height: 100%;
        object-fit: contain;
    }

    .cut {
        position: absolute;
        top: 0;
        left: 0;
        width: 100px;
        height: 100px;
        border: 1px solid red;
        box-sizing: border-box;
        box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
    }

    .cut .move {
        position: absolute;
        top: 100%;
        left: 100%;
        margin-top: -2px;
        margin-left: -2px;
        width: 5px;
        height: 5px;
        background-color: red;
        font-size: 14px;
        white-space: nowrap;
        cursor: move;
    }

    canvas {
        border: 1px dashed #000;
    }
</style>

<body>
    <input type="file"><br>
    <div class="content" style="display: flex;align-items: start;gap:20px">
        <div class="preview">
            <img src="../../../assets/img/wallhaven-x1xy9l.jpg" alt="">
            <div class="cut">
                <div class="move"></div>
            </div>
        </div>
        <canvas class="canvas"></canvas>
    </div>
    <button onclick="cropImage()">裁剪</button>
    <script>


        const cutInfo = {
            x: 0, // 裁剪起始x轴位置
            y: 0, // 裁剪起始y轴位置
            width: 100, // 裁剪区域宽度
            height: 100, // 裁剪区域高度
            outputWidth: 400, // 裁剪后图片宽度
            outputHeight: 400, // 裁剪后图片高度
            scale: 1, // 裁剪区域缩放比例  
            imgWidth: 0, // 图片实际宽度
            imgHeight: 0, // 图片实际高度 
            emptyX: 0, // 图片X轴偏移位置
            emptyY: 0, // 图片Y轴偏移位置
        }
        let mouseX = 0;
        let mouseY = 0;

        const canvas = document.querySelector('.canvas');
        canvas.width = cutInfo.outputWidth;
        canvas.height = cutInfo.outputHeight;
        const ctx = canvas.getContext('2d');
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        const cutEl = document.querySelector('.cut');
        const moveEl = document.querySelector('.move');

        // 获取img元素实际宽高
        const img = document.querySelector('img');
        img.onload = function () {
            // 计算图片原始尺寸宽高比
            let ratio = this.naturalWidth / this.naturalHeight;
            // 获取图片实际宽高
            if (ratio >= 1) {
                cutInfo.imgWidth = this.width;
                cutInfo.imgHeight = Math.floor(this.width / ratio);
            } else {
                cutInfo.imgHeight = this.height;
                cutInfo.imgWidth = Math.floor(this.height * ratio);
            }
            // 计算裁剪区域图片相对原始尺寸的缩放比例
            cutInfo.ratio = cutInfo.imgWidth / this.naturalWidth
            cutInfo.emptyX = (canvas.width - cutInfo.imgWidth) / 2
            cutInfo.emptyY = (canvas.height - cutInfo.imgHeight) / 2
            preview()
        }

        // 监听file改变
        document.querySelector('input').addEventListener('change', function (event) {
            const file = event.target.files[0];
            if (!file) return;
            cutEl.style.left = '0px';
            cutEl.style.top = '0px';
            const reader = new FileReader();
            reader.onload = function (e) {
                document.querySelector('img').src = e.target.result;
            };
            reader.readAsDataURL(file);

        });

        // 拖动cut,相对 preview 元素移动
        cutEl.addEventListener('mousedown', function (e) {
            mouseX = e.clientX;
            mouseY = e.clientY;
            if (e.target === moveEl) {
                // 缩放 cutEl ,且高宽比1:1
                document.addEventListener('mousemove', function (e) {
                    let size = Math.max(e.clientX - mouseX, e.clientY - mouseY);
                    mouseX = e.clientX;
                    mouseY = e.clientY;
                    let newWidth = cutEl.offsetWidth + size;
                    let newHeight = cutEl.offsetHeight + size;
                    // 限制最小宽高100, 最大宽高不能超出图片范围
                    if (newWidth < 100) newWidth = 100;
                    if (newHeight < 100) newHeight = 100;
                    let maxSize = Math.min(500 - cutEl.offsetLeft, 500 - cutEl.offsetTop)
                    if (newWidth > maxSize) newWidth = maxSize;
                    if (newHeight > maxSize) newHeight = maxSize;
                    cutEl.style.width = newWidth + 'px';
                    cutEl.style.height = newHeight + 'px';
                    let moveFn = arguments.callee
                    document.addEventListener('mouseup', function (e) {
                        document.removeEventListener('mousemove', moveFn)
                    })
                    cutInfo.width = newWidth;
                    cutInfo.height = newHeight;
                    preview()
                })

            } else {
                document.addEventListener('mousemove', function (e) {
                    let left = cutEl.offsetLeft + e.clientX - mouseX;
                    let top = cutEl.offsetTop + e.clientY - mouseY;
                    mouseX = e.clientX;
                    mouseY = e.clientY;
                    // 限制移动范围,不能移除图片范围
                    let maxLeft = 500 - cutInfo.width
                    let maxTop = 500 - cutInfo.height
                    if (top < 0) top = 0;
                    if (left < 0) left = 0;
                    if (top > maxTop) top = maxTop;
                    if (left > maxLeft) left = maxLeft;

                    cutEl.style.left = left + 'px';
                    cutEl.style.top = top + 'px';
                    let moveFn = arguments.callee
                    document.addEventListener('mouseup', function (e) {
                        document.removeEventListener('mousemove', moveFn)
                    })
                    cutInfo.x = left;
                    cutInfo.y = top;
                    preview()
                })
            }


        })


        // 实时预览
        function preview() {
            // 这里传递了 9 个参数，是用来裁剪图片的
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(
                document.querySelector('img'),
                (cutInfo.x - cutInfo.emptyX) / cutInfo.ratio, (cutInfo.y - cutInfo.emptyY) / cutInfo.ratio,
                cutInfo.width / cutInfo.ratio, cutInfo.height / cutInfo.ratio,
                0, 0,
                cutInfo.outputWidth, cutInfo.outputHeight
            );
        }

        // 点击裁剪
        function cropImage() {
            preview()

            canvas.toBlob(blob => {
                const a = document.createElement('a');
                a.download = `头像.png`;
                a.href = URL.createObjectURL(blob);
                a.click();
                a.remove();
            });

        }

    </script>
</body>

</html>